﻿using MongoDB.Bson;
using MongoDB.Driver;
using SimpleInjector;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer;
using Sustainalytics.Entities;
using Sustainalytics.Entities.Governance;
using Sustainalytics.Synchronization.Interface;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace Sustainalytics.Governance.Synchronization
{
    public class GovernanceSync
    {
        private readonly IMongoCollection<CorporateData> _corporateDataCollection;
        private readonly IMongoCollection<CompanyProfileResearchState> _companyProfileResearchStateCollection;
        private readonly IMongoCollection<SharedEntities.CorporateGovernanceResearch.CorporateGovernanceResearchProfile> _corporateGovernanceResearchProfileCollection;
        private readonly IMongoCollection<GovernanceCompanyProfile> _governanceCompanyProfileCollection;
        private readonly IMongoCollection<GovernanceRegion> _governanceRegionCollection;

        private readonly IMongoCollection<GovernanceIndicator> _governanceIndicatorCollection;
        private readonly IMongoCollection<GovernanceInsightInputFile> _governanceInsightInputFileCollection;

        protected Container SimpleInjectorContainer;


        public GovernanceSync(
            IMongoCollection<CorporateData> corporateDataCollection,
            IMongoCollection<CompanyProfileResearchState> companyProfileResearchStateCollection,
            IMongoCollection<SharedEntities.CorporateGovernanceResearch.CorporateGovernanceResearchProfile> corporateGovernanceResearchProfileCollection,
            IMongoCollection<GovernanceCompanyProfile> governanceCompanyProfileCollection,
            IMongoCollection<GovernanceRegion> governanceRegionCollection,
            IMongoCollection<GovernanceIndicator> governanceIndicatorCollection,
            IMongoCollection<GovernanceInsightInputFile> governanceInsightInputFileCollection
            )
        {
            _corporateDataCollection = corporateDataCollection;
            _companyProfileResearchStateCollection = companyProfileResearchStateCollection;
            _corporateGovernanceResearchProfileCollection = corporateGovernanceResearchProfileCollection;
            _governanceCompanyProfileCollection = governanceCompanyProfileCollection;
            _governanceRegionCollection = governanceRegionCollection;
            _governanceIndicatorCollection = governanceIndicatorCollection;
            _governanceInsightInputFileCollection = governanceInsightInputFileCollection;
        }

        public SyncContext Start(SyncContext syncContext)
        {
            // check for task cancellation
            syncContext.Token.ThrowIfCancellationRequested();


            SimpleInjectorContainer = SimpleInjectorConfig.Register(new SyncContextLogger(syncContext), syncContext.GAConnectionStrings);

            var corporateGovernanceResearchImport = SimpleInjectorContainer.GetInstance<CorporateGovernanceResearchImport>();
            corporateGovernanceResearchImport.StartAsync().Wait();

            UpdateCompanyProfiles(syncContext);
            UpdateRegionIds(syncContext);

            // oare mai este nevoie?
            UpdateAnalystViewAndOutlook(syncContext);

            //The company profile will be marked in corporate data with ResearchState.ResearchInProgress because it has no region.
            UpdateGovernanceResearchFlagInCorporateData(syncContext, corporateGovernanceResearchImport.ActiveResearchCompanyIds);


            return syncContext;
        }


        private void UpdateGovernanceResearchFlagInCorporateData(SyncContext syncContext, List<Guid> activeResearchCompanyIds)
        {
            var companyIds = _corporateGovernanceResearchProfileCollection
                .Find(_ => true)
                .Project(x => x.Id)
                .ToList();

            var missingCompanyIds = activeResearchCompanyIds.Where(id => companyIds.All(x => x != id));


            var emptyRegionCompanies = _corporateDataCollection.Find(cd => companyIds.Contains(cd.Id) && (cd.GovernanceRegionCode == string.Empty || cd.GovernanceRegionCode == null))
                .Project(cd => new { cd.Id, cd.CompanyName })
                .ToList();

            emptyRegionCompanies.ForEach(c => syncContext.Log(String.Format("The company \"{0}\" will be marked without governance research because it has no region.", c.CompanyName)));

            var emptyRegionCompanyIds = emptyRegionCompanies.Select(a => a.Id);
            syncContext.Log("Updating research flag with false");


            var ids = missingCompanyIds.Union(emptyRegionCompanyIds).ToList();

            SetResearchStateInProgress(ids);

            syncContext.Log("Governance sync step done.");
        }

        private void SetResearchStateInProgress(List<Guid> companyIds)
        {

            var companyProfileUpdateDefinition = Builders<CompanyProfileResearchState>.Update.Set(x => x.CorporateGovernanceResearchState.ResearchState, ResearchState.ResearchInProgress);

            _companyProfileResearchStateCollection.UpdateMany(cp => companyIds.Contains(cp.Id), companyProfileUpdateDefinition);


            var corporateDataUpdateDefinition = Builders<CorporateData>.Update
                .Set(x => x.CorporateGovernanceResearchState.ResearchState, ResearchState.ResearchInProgress);

            _corporateDataCollection.UpdateMany(cp => companyIds.Contains(cp.Id), corporateDataUpdateDefinition);
        }

        private void UpdateCompanyProfiles(SyncContext syncContext)
        {
            var sw = Stopwatch.StartNew();

            var companyIds = _corporateGovernanceResearchProfileCollection
                .Find(_ => true)
                .Project(x => x.Id)
                .ToList();

            var insightInputFiles = _governanceInsightInputFileCollection
                .Find(x => companyIds.Contains(x.CompanyId))
                .ToList()
                .GroupBy(x => x.CompanyId)
                .ToDictionary(x => x.Key, x => x.Select(insight => new GovernanceInsight
                                                        {
                                                            Id = insight.Id,
                                                            CreatedDate = insight.CreationTimeStamp,
                                                            CreatedBy = insight.CreatedBy,
                                                            PublicationDate = insight.PublicationDate,
                                                            Title = insight.Title,
                                                            FileId = insight.FileId
                                                        }).ToList());

            var corporateData = _corporateDataCollection
                .Find(
                    cd => companyIds.Contains(cd.Id) && 
                    cd.GovernanceRegionCode != string.Empty && 
                    cd.GovernanceRegionCode != null)
                .Project(cd => new
                {
                    cd.Id,
                    cd.CompanyName,
                    cd.CompanyNameUrlFriendly,
                    cd.Country,
                    cd.GovernanceRegionCode,
                    cd.GovernanceRegionName,
                    cd.PeerGroup,
                    cd.MainExchange,
                    cd.MainTicker,
                    cd.MarketCapitalization,
                    cd.TotalEmployees
                }).ToList();

            if (!corporateData.Any())
                return;

            var companyProfiles = corporateData.Select(cd => new GovernanceCompanyProfile
            {
                Id = cd.Id,
                CompanyName = cd.CompanyName,
                CompanyNameSafeUrl = cd.CompanyNameUrlFriendly,
                Domicile = cd.Country,
                RegionCode = cd.GovernanceRegionCode,
                RegionName = cd.GovernanceRegionName,
                Industry = cd.PeerGroup,
                Ticker = cd.MainExchange + ":" + cd.MainTicker,
                MarketCapitalization = cd.MarketCapitalization,
                Employees = cd.TotalEmployees,
                IsValidReport = false,
                Insights = insightInputFiles.ContainsKey(cd.Id) ? insightInputFiles[cd.Id] : null,
            }).ToList();

            _governanceCompanyProfileCollection.Database.DropCollection(typeof(GovernanceCompanyProfile).Name);
            _governanceCompanyProfileCollection.InsertMany(companyProfiles);


            sw.Stop();
            syncContext.Log($"{companyProfiles.Count} GovernanceCompanyProfile entities updated. Elapsed time: {sw.Elapsed}");
        }



        private void UpdateRegionIds(SyncContext syncContext)
        {
            var sw = Stopwatch.StartNew();

            var regions = _governanceRegionCollection.Find(new BsonDocument()).ToList()
                .ToDictionary(k => k.Code, v => v.Id);

            var companyProfiles = _governanceCompanyProfileCollection.Find(new BsonDocument()).ToList();

            companyProfiles.ForEach(cp =>
            {
                if (string.IsNullOrEmpty(cp.RegionCode))
                {
                    syncContext.Log(string.Format("Region Code for company {0} with ID: {1} is null or empty!",
                        cp.CompanyName, cp.Id));

                    return;
                }

                var regionId = regions.ContainsKey(cp.RegionCode)
                    ? regions[cp.RegionCode]
                    : Guid.Empty;

                _governanceCompanyProfileCollection.UpdateOne(x => x.Id == cp.Id, 
                    Builders<GovernanceCompanyProfile>.Update.Set(x => x.RegionId, regionId));
            });

            sw.Stop();
            syncContext.Log(string.Format("{0} GovernanceCompanyProfile RegionId's updated. Elapsed time: {1}",
                companyProfiles.Count, sw.Elapsed));
        }

        private void UpdateAnalystViewAndOutlook(SyncContext syncContext)
        {
            var sw = Stopwatch.StartNew();

            var companyProfiles = _corporateGovernanceResearchProfileCollection
                .Find(_ => true)
                .Project<SharedEntities.CorporateGovernanceResearch.CorporateGovernanceResearchProfile>(
                Builders<SharedEntities.CorporateGovernanceResearch.CorporateGovernanceResearchProfile>.Projection
                    .Include(x => x.Id)
                    .Include(x => x.AnalystView)
                    .Include(x => x.Outlook)
                    .Include(x => x.FullProfile))
                .ToList();

            Parallel.ForEach(companyProfiles, new ParallelOptions() { }, profile =>
            {
                var filter = Builders<GovernanceCompanyProfile>.Filter.Eq(cp => cp.Id, profile.Id);
                var update = Builders<GovernanceCompanyProfile>.Update
                        .Set(cp => cp.AnalystView, profile.AnalystView.Value)
                        .Set(cp => cp.FullProfile, profile.FullProfile)
                        .Set(cp => cp.Outlook, (OutlookType?)profile.Outlook?.Value);

                _governanceCompanyProfileCollection.UpdateOne(filter, update);
            });

            sw.Stop();

            syncContext.Log($"{companyProfiles.Count} GovernanceCompanyProfile entities updated with Outlook and AnalystView. Elapsed time: {sw.Elapsed}");
        }

    }
}
